#include "luaengine.h"

#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <iostream>
#include <iomanip>
#include <time.h>
#include <stdio.h>

#include "macro.h"
#include "luaglue.h"
#include "filesystem.h"
#include "logger.h"
#include "color.h"
#include "misc.h"
#include "error.h"

/* INTRO_TEXT is rot13'd to prevent those few idiots
	who have been hex-editing their own URL in-place.
	It doesn't prevent modification, but makes
	it a bit harder to find to said idiots.
*/
const char *INTRO_TEXT = "FbyneFgevxr Fbsgjner\nuggc://jjj.fbynefgevxr.arg";




LuaEngine::LuaEngine()
{
    lstate = NULL;
    keyboardLayoutName = "<UNKNOWN>";

	flashOnError = true;
}

int LuaEngine::keyboardModuleLoaded()
{
    int loaded = true;
    lua_getglobal(lstate, "key");
    if( !lua_istable(lstate, -1) ) {
        Logger::instance()->add("Warning: Global \'key\' is not a table.");
        loaded = false; }
    else
    {
        lua_pushstring(lstate, "layout");
        lua_gettable(lstate, -2);
        if( !lua_isstring(lstate, -1) )
        {
            Logger::instance()->add(
                "Warning: Keyboard module does not define \'layout\'.");
            loaded = false;
        }
        else
        {
            keyboardLayoutName = (std::string)lua_tostring(lstate, -1);
        }
        lua_pop(lstate, 1);
    }

    return loaded;
}

int LuaEngine::registerEncryptionKeys()
{
    std::string libpath = getAppPath();
    libpath += "\\";
    libpath += LIB_DIR;

	if( !fileExists( (libpath+NET_FILE).c_str() ) ) {
		Logger::instance()->add("Creating random default encryption key\n");
		createEncryptionKeysFile(); }

    int success = true;
    Macro::instance()->netFlushKeys();
    if( !run( (libpath + NET_FILE).c_str() ) ) {
        success = false; }
    else
    {
        Macro::instance()->netRegisterKeys();
    }

    return success;
}

/* Generate a new encryption keys file */
int LuaEngine::createEncryptionKeysFile()
{
    std::string libpath = getAppPath();
    libpath += "\\";
    libpath += LIB_DIR;

	const char *fmt = "netPushKey(\"default\", \"%s\");";
	std::ofstream outfile;
	outfile.open((libpath + NET_FILE).c_str());

	if( !outfile.is_open() )
		return false;

	char buffer[256];
	snprintf(buffer, 256, fmt, randomString(64, RS_ALL).c_str());

	outfile << buffer;
	return true;
}

int LuaEngine::getConfigInt(const char *name, int _default)
{
	lua_getglobal(lstate, name);
	if( lua_isnumber(lstate, -1) )
		return (int)lua_tointeger(lstate, -1);
	else
		return _default;
}

int LuaEngine::getConfigBool(const char *name, int _default)
{
	lua_getglobal(lstate, name);
	if( lua_isboolean(lstate, -1) )
		return (int)lua_toboolean(lstate, -1);
	else
		return _default;
}

std::string LuaEngine::getConfigStr(const char *name, const char *_default)
{
	lua_getglobal(lstate, name);
	if( lua_isstring(lstate, -1) )
		return (char *)lua_tostring(lstate, -1);
	else
		return _default;
}

void LuaEngine::luaEngineHook(lua_State *lstate, lua_Debug *arg)
{
	switch(arg->event)
	{
		case LUA_HOOKLINE:
			if( !mainRunning && lstate)
			{
				luaL_error(lstate, "Environment forcefully killed by user.");
			}
		break;
	}
}


int LuaEngine::init(CLI_OPTIONS *pclio)
{
    std::string tmp = getAppPath();
    tmp += "\\";
    tmp += LOG_FILE;
    if( !Logger::instance()->open(tmp.c_str()) )
        fprintf(stderr, "WARNING: Error opening log file.\n");

    /* Gather information about the processor and operating system */
	HKEY hKey;
	char szProcessorSpeed[32];
	int rError = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
		"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0",
		0, KEY_READ, &hKey);
	if( rError == ERROR_SUCCESS )
	{
		DWORD mhz = _MAX_PATH;
		DWORD buffSize = _MAX_PATH;
		RegQueryValueEx(hKey, "~MHz", NULL, NULL, (BYTE *)&mhz, &buffSize);
		snprintf((char*)&szProcessorSpeed, 32, "@%dMHz", (int)mhz);
	}
	else
		strcpy((char *)&szProcessorSpeed, "@Unknown speed");

	Logger::instance()->add("Processor Type: %s %s, OS: %s",
        getProcessorType().c_str(), (char *)&szProcessorSpeed,
		getOsName().c_str());


	/* Gather information about the user */
	DWORD userGroup = getUserPriv();
	std::string userGroupName = getPrivName(userGroup);
	std::string userGroupMsg = "User privilege level: " + userGroupName;
	Logger::instance()->add(userGroupMsg.c_str());


    /* Open and initialize Lua state */
    lstate = luaL_newstate();
    if( lstate == NULL )
    {
        Logger::instance()->add("Failed to initialize Lua.");
        return false;
    }

    /* Run configurations */
    std::string libpath = getAppPath();
    libpath += "\\";
    libpath += LIB_DIR;

    if( !run(CONFIG_FILE) )
    {
        Logger::instance()->add("Failed to run configuration file \'%s\'.\n",
            CONFIG_FILE);
        //return false;
    }

	/* Load config vars */
    CONFIG_OPTIONS co;
	co.windowWidth = getConfigInt(CONFIG_VAR_CLIWIDTH, 0);
	co.windowHeight = getConfigInt(CONFIG_VAR_CLIHEIGHT, 0);
	co.flashOnError = getConfigBool(CONFIG_VAR_FLASHONERROR, true);
	co.useStealth = getConfigBool(CONFIG_VAR_STEALTH, false);
    co.enableSound = getConfigBool(CONFIG_VAR_ENABLESOUND, true);

	if( co.windowWidth < 0 ) co.windowWidth = 0;
	if( co.windowHeight < 0 ) co.windowHeight = 0;

    flashOnError = co.flashOnError;

    /* Initialize macro system */
    if( !Macro::instance()->init(pclio, &co) )
    {
        Logger::instance()->add("Failed to initialize macro system.");
        return false;
    }


    /* Open libraries */
	luaL_openlibs(lstate);
	luaopen_debug(lstate);
	//luaopen_lxp(lstate);

    /* Register functions, export metatables */
    registerGlues(lstate);
    Logger::instance()->add("Lua glues exported");

	/* Print version info and intro text */
	int major = VERSION_NUMBER / 100;
	int minor = VERSION_NUMBER % 100;
	long bufSize = 1024;
	char buf[bufSize];

		#if BETA_NUMBER > 0
			/* Beta version */
			snprintf((char*)&buf, bufSize, "MicroMacro v%i.%02i beta %i", major,
			minor, BETA_NUMBER);
		#else
			/* "Release" version */
			snprintf((char*)&buf, bufSize, "MicroMacro v%i.%02i", major, minor);
		#endif


	char *introtext = new char[ strlen(INTRO_TEXT) + 1 ];
	if( introtext == NULL )
		allocationError();
	strcpy(introtext, INTRO_TEXT);
	rot13(introtext);

	Logger::instance()->add((char*)&buf);
	color_printf(COLOR_GREEN, "%s\n%s\n", (char*)&buf, introtext);
	memset(introtext, 0, strlen(INTRO_TEXT));

	delete [] introtext;
	introtext = NULL;


	/* Handle config vars not handled by the library */
	if( co.useStealth )
	{
		std::string winName = randomString(16, RS_NUMBERS | RS_LETTERS);
		SetConsoleTitle(winName.c_str());
	}
	else
	{/* Set window name */
		SetConsoleTitle(buf);
	}

	// 'buf' is no longer needed. Get rid of it's contents to
	// prevent detection of this string by other processes.
	memset(buf, 0, bufSize);


	if( co.windowWidth != 0 || co.windowHeight != 0 )
	{
		HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
		SMALL_RECT rect;
		rect.Top = 0; rect.Left = 0;
		rect.Right = co.windowWidth - 1; rect.Bottom = co.windowHeight - 1;

		bool success = SetConsoleWindowInfo(hOut, TRUE, &rect);

		if( ! success )
		{
			CONSOLE_SCREEN_BUFFER_INFO info;
			GetConsoleScreenBufferInfo(hOut, &info);
			char msg[512];
			snprintf((char*)&msg, 512, "Failed to change window size: "
				"Exceeds maximum of (%d, %d).",
				info.dwMaximumWindowSize.X, info.dwMaximumWindowSize.Y);

			printf((char*)&msg); printf("\n\n");
			Logger::instance()->add((char*)&msg);
		}
	}

	// Remove always-on-top if set
	SetWindowPos(Macro::instance()->getHwnd(), HWND_NOTOPMOST,
		0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE);


    if( !run( (libpath + LIB_FILE).c_str() ) )
    {
        Logger::instance()->add("Failed to run library file \'%s\'.\n",
            LIB_FILE);
        //return false;
    }

    /* Make sure a keyboard module is defined. */
    if( keyboardModuleLoaded() )
    {
        Logger::instance()->add("Keyboard layout: %s",
            keyboardLayoutName.c_str());
    }
    else { /* We don't really care... */ }

    /* Register keys (only those from net.lua */
    registerEncryptionKeys();

    /* Install plugins */
    //installAllPlugins();

    /* Setup networking */
    Macro::instance()->netInit();
    return true;
}

int LuaEngine::reinit(CLI_OPTIONS *pclio)
{
    /* Cleanup garbage first */
    cleanup();

	// Remove always-on-top if set
	SetWindowPos(Macro::instance()->getHwnd(), HWND_NOTOPMOST,
		0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE);

    /* Re-initialize Lua state */
    lstate = luaL_newstate();
    if( lstate == NULL ) {
        Logger::instance()->add("Failed to re-create Lua State\n");
        return false; }

    /* Run configurations */
    std::string libpath = getAppPath();
    libpath += "\\";
    libpath += LIB_DIR;

    if( !run(CONFIG_FILE) )
        Logger::instance()->add("Failed to run configuration file \'%s\'.\n",
            CONFIG_FILE);

	/* Re-load config vars */
	CONFIG_OPTIONS co;
	co.flashOnError = getConfigBool(CONFIG_VAR_FLASHONERROR, true);
	co.useStealth = getConfigBool(CONFIG_VAR_STEALTH, true);

	flashOnError = co.flashOnError;

	if( co.useStealth )
	{
		/* Reset to a new random window title */
		std::string winName = randomString(16, RS_NUMBERS | RS_LETTERS);
		SetConsoleTitle(winName.c_str());
	}
	else
	{
		/* Reset default window title */
		int major = VERSION_NUMBER / 100;
		int minor = VERSION_NUMBER % 100;
		long bufSize = 1024;
		char buf[bufSize];
		#if BETA_NUMBER > 0
			/* Beta version */
			snprintf((char*)&buf, bufSize, "MicroMacro v%i.%02i beta %i",
			major, minor, BETA_NUMBER);
		#else
			/* "Relase" version */
			snprintf((char*)&buf, bufSize, "MicroMacro v%i.%02i", major, minor);
		#endif

		SetConsoleTitle(buf);
		memset(buf, 0, bufSize);
	}

    /* Re-initialize macro system */
    if( !Macro::instance()->init(pclio, &co) ) {
        Logger::instance()->add("Failed to re-create macro system\n");
        return false; }

    /* Open libraries */
	luaL_openlibs(lstate);
	luaopen_debug(lstate);
	//luaopen_lxp(lstate);

    /* Register functions, export metatables */
    registerGlues(lstate);

    if( !run( (libpath + LIB_FILE).c_str() ) )
        Logger::instance()->add("Failed to run library file \'%s\'.\n",
            LIB_FILE);

    /* Make sure a keyboard module is defined. */
    if( !keyboardModuleLoaded() )
    { /* We don't really care... */ }

    /* Register keys (only those from net.lua */
    registerEncryptionKeys();

    /* Setup networking */
    Macro::instance()->netInit();

    return true;
}

int LuaEngine::cleanup()
{
	#ifdef	DISPLAY_DEBUG_MESSAGES
	Logger::instance()->add("DEBUG: Closing Lua state");
	#endif

    if( lstate )
        lua_close(lstate);

	#ifdef	DISPLAY_DEBUG_MESSAGES
	Logger::instance()->add("DEBUG: Closed. Cleaning up macro instance.");
	#endif

    if( !Macro::instance()->cleanup() )
        return false;

	#ifdef	DISPLAY_DEBUG_MESSAGES
	Logger::instance()->add("DEBUG: Done cleaning up macro instance.");
	#endif
    return true;
}

int LuaEngine::run(const char *filename)
{
	int failstate = luaL_dofile(lstate, filename);

    if( failstate ) {
        std::string err = lua_tostring(lstate, lua_gettop(lstate));
        fprintf(stderr, "%s\n", err.c_str());
        Logger::instance()->add(err.c_str());

        return false;
    }

    return true;
}

int LuaEngine::run(std::string script, std::vector<std::string> &argline)
{
	std::string filename = getFileName(script);

	// Apply .lua extension
	if( filename.find(".") == std::string::npos && !fileExists(filename) ) {
		filename += ".lua";
		script += ".lua";
	}

    printf("Opening %s...\n", filename.c_str());
    printf("Starting script execution - Press CTRL+C to exit.\n");
    printf("Press CTRL+L to cancel execution and load a new script.\n");

    setColor(COLOR_GREEN);
	// It seems the previous 'cout' line here was causing trouble under Wine.
	// Instead, use printf to avoid any problems.
	char splitLine80[81];
	memset((char*)&splitLine80, '-', 80);
	splitLine80[79] = '\n';
	splitLine80[80] = 0;

	printf((char*)&splitLine80);


    setColor(COLOR_LIGHTGRAY);

    Logger::instance()->add("Executing script \'%s\'",
        filename.c_str());
    Logger::instance()->add_raw((char*)&splitLine80);
	Logger::instance()->add_raw("\n\n");


    int failstate = luaL_loadfile(lstate, script.c_str());

    // No error loading the file, so pass it the arguments and run it
    if( !failstate )
    {
        lua_newtable(lstate);

		/* Push the script name for args[1] */
		lua_pushnumber(lstate, 1); // key
		lua_pushstring(lstate, script.c_str()); // value
		lua_settable(lstate, -3);

        for(unsigned int i = 0; i < argline.size(); i++)
        {
            lua_pushnumber(lstate, i + 2); // key
            lua_pushstring(lstate, argline[i].c_str()); // value
            lua_settable(lstate, -3);
        }
        lua_setglobal(lstate, "args");

        // Now actually run the script
		lua_sethook(lstate, &luaEngineHook, LUA_MASKLINE, 10); // Check if we should break execution
        failstate = lua_pcall(lstate, 0, LUA_MULTRET, 0);

		#ifdef	DISPLAY_DEBUG_MESSAGES
		Logger::instance()->add("DEBUG: Script execution complete.");
		#endif

		if( !mainRunning )
			failstate = 0; // Pretend like no error occured.
    }

	#ifdef	DISPLAY_DEBUG_MESSAGES
	Logger::instance()->add("DEBUG: Check fail state.");
	#endif

    if( failstate )
    {
		time_t rawtime;
		struct tm * timeinfo;
		time( &rawtime );
		timeinfo = localtime ( &rawtime );
		char timeString[32];

		int hour;
		char amStr[3];
		if( timeinfo->tm_hour > 12 )
		{
			hour = timeinfo->tm_hour - 12;
			strcpy((char*)&amStr, "pm");
		} else
		{
			hour = timeinfo->tm_hour;
			strcpy((char*)&amStr, "am");
		}

		snprintf((char*)&timeString, 256, "%d:%d%s", hour, timeinfo->tm_min, amStr);
        std::string err = lua_tostring(lstate, lua_gettop(lstate));
        color_printf(COLOR_YELLOW, "%s - %s\n", timeString, err.c_str());
        Logger::instance()->add("%s", err.c_str());

        switch(failstate)
        {
            case LUA_ERRRUN:
                Logger::instance()->add("Execution error: Runtime error");
            break;

            case LUA_ERRMEM:
                Logger::instance()->add("Execution error: Memory error");
            break;

            case LUA_ERRSYNTAX:
                Logger::instance()->add("Execution error: Syntax error");
            break;

            case LUA_ERRFILE:
                Logger::instance()->add("Execution error: Error reading file");
            break;

            case LUA_ERRERR:
                Logger::instance()->add("Execution error: Error handler error");
            break;

            default:
                Logger::instance()->add("Default error: Unknown error occured");
            break;
        }
    }
    else
        Logger::instance()->add("Execution success\n");

	#ifdef	DISPLAY_DEBUG_MESSAGES
	Logger::instance()->add("DEBUG: Flash window.");
	#endif

	if( flashOnError )
	{
		FLASHWINFO fwi;
		memset(&fwi, sizeof(FLASHWINFO), 0);
		fwi.hwnd = ::getAppHwnd();
		fwi.dwFlags = FLASHW_ALL | FLASHW_TIMERNOFG;
		fwi.cbSize = sizeof(FLASHWINFO);
		fwi.dwTimeout = 0;
		FlashWindowEx(&fwi);
	}

	#ifdef	DISPLAY_DEBUG_MESSAGES
	Logger::instance()->add("DEBUG: Return from function \'%s\'.", __FUNCTION__);
	#endif

    printf("\n\n");
    return failstate;
}

int LuaEngine::checkCommands(std::string &script,
std::vector<std::string> &argline)
{
    if( script.compare("exit") == 0 )
        exit(0); // Exit command
    else if( script.compare("clear") == 0 )
    {
        clearCliScreen(); // Clear command
        return true;
    }
	else if( script.compare("buildinfo") == 0 )
	{
		/* Print version info and intro text */
		int major = VERSION_NUMBER / 100;
		int minor = VERSION_NUMBER % 100;
		long bufSize = 1024;
		char buf[bufSize];

		#if BETA_NUMBER > 0
			/* Beta version */
			snprintf((char*)&buf, bufSize, "MicroMacro v%i.%02i beta %i", major,
			minor, BETA_NUMBER);
		#else
			/* "Release" version */
			snprintf((char*)&buf, bufSize, "MicroMacro v%i.%02i", major, minor);
		#endif
		printf("Lua version: %s\n%s\nCompiled %s %s\n\n",
			LUA_VERSION, (char*)&buf, __DATE__, __TIME__);

		return true;
	}


    std::string cmdfile = getAppPath();
    cmdfile += "\\";
    cmdfile += COMMAND_DIR;
    cmdfile += script;
    cmdfile += ".lua";

    if( fileExists(cmdfile) )
    { // Run the command file
        script = cmdfile;
        run(script, argline);
        return true;
    }

    return false;
}

lua_State *LuaEngine::getLuaState()
{
    return lstate;
}


